package com.csw.rediscachedemo.aspect;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
    //    @Autowired
//    @Qualifier("RedissonClient")
//    private static RedissonClient redissonClient;

    private static RedissonClient redissonClient = RedLock.getRedissonClient();


    private static StringRedisTemplate stringRedisTemplate;

    public RedisUtil(
        StringRedisTemplate stringRedisTemplate
    ){
        RedisUtil.stringRedisTemplate = stringRedisTemplate;
    }

    public static void main(String[] args) throws Exception {

        //单启动不能测试aop,把下面这个方法放到正常controller里才行
        selectTest("1");
    }

    /**
     * 更新数据库
     * 1-延迟双删
     * 2-canal(阿里的一款开源框架)，通过该框架可以对MySQL的binlog进行订阅，而canal正是模仿了mysql的slave数据库的备份请求，使得Redis的数据更新达到了相同的效果。
     *
     * @param key
     * @param data
     * @throws InterruptedException
     */
    public void writeRedis(String keyT) throws Exception {
        //先删除
        stringRedisTemplate.delete(keyT);
        //更新数据库
        updateData("id");
        Thread.sleep(500);
        //延迟再次删除
        stringRedisTemplate.delete(keyT);
    }

    private void updateData(String id) {
    }

    @RedisCache
    public static String selectTest(String id) {
        System.out.println("进入方法了");
        return "1122334455";
    }

    /**
     * 查询redis-双重检索机制
     * [当方法的查询和更新字段在一个方法里（高并发会出问题），必须用分布式锁]
     *
     * @param keyT  键
     * @param dataValue 数据库的值
     * @param seconds
     */
    public static Object selectRedis(String keyT, long seconds, ProceedingJoinPoint point) throws Exception {
        boolean aa = stringRedisTemplate.hasKey(keyT);
        if (aa) {
            //如果缓存有值就直接返回
            Object obj = stringRedisTemplate.opsForValue().get(keyT);
            return obj;
        } else {
            // 获取锁
            //加锁的key和商品的key不能是一个
            RLock lock = redissonClient.getLock(keyT + ":redisLock");
            try {
                //500ms拿不到锁, 就认为获取锁失败。10000ms即10s是锁失效时间。保证不同服务请求都阻塞等待，或者快速响应失败
                //解决缓存击穿1--     热点key 失效，短时间内有大量请求
                boolean b = lock.tryLock(1 * 60, 1 * 60, TimeUnit.SECONDS);
                if (b) {
                    //解决缓存击穿2
                    //在锁里面再次判断，是为了防止后续其他线程因为缓存失效重复更新redis缓存
                    boolean bb = stringRedisTemplate.hasKey(keyT);
                    if (bb == false) {
                        //模拟查询数据库，将值塞进去
                        //走完方法，获取返回结果
                        Object proceed = point.proceed();
                        if (proceed == null) {
                            //解决缓存穿透，数据在缓存和数据库里都不存在；要设置比较长的过期时间，设置空值
                            //如果说有大量不同的key恶意攻击，这应该是个安全问题，调用接口要权限校验
                            stringRedisTemplate.opsForValue().set(keyT, "", 1 * 60, TimeUnit.SECONDS);
                        } else {
                            //需要设置不同的过期时间，解决缓存雪崩-大量的key,同时失效；
                            double inZi = 0.7;
                            int v = (int) (Math.random() * (seconds * (1 - inZi) * 2) + seconds * inZi);
                            stringRedisTemplate.opsForValue().set(keyT, JSON.toJSONString(proceed), v, TimeUnit.SECONDS);
                            return proceed;
                        }
                    } else {
                        Object obj = stringRedisTemplate.opsForValue().get(keyT);
                        return obj;
                    }
                } else {
                    return "";
                }
            } catch (Exception e) {
                e.printStackTrace();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            } finally {

                lock.unlock();
            }
        }
        return "";
    }

}
